package zgh.singleton;

/**
 * 单例模式的定义与特点
 * 单例(Singleton)模式的定义：指一个类只有一个实例，且该类能自行创建这个实例的一种模式。例如：Windows中只能打开一个任务管理器，
 * 这样就可以避免因打开多个任务管理器而造成内存资源的浪费，或出现各个窗口显示内容的不一致等错误
 * <p>
 * 单例模式有3个特点
 * 1. 单例类只能一个实例对象
 * 2. 该单例对象必须由单例类自行创建
 * 3. 单例类对外提供一个访问该单例的全局访问点
 * <p>
 * 单例模式的优点和缺点
 * 优点：
 * 1. 单例模式可以保证内存里只有一个实例，减少了内存开销
 * 2. 可以避免对资源的多重占用
 * 3. 单例模式设置全局访问点，可以优化和共享资源的访问
 * <p>
 * 缺点：
 * 1. 单例模式一般没有接口，扩展困难，如果要扩展，则除了修改原来的代码，没有第二种途径，违背开闭原则
 * 2. 在并发测试中，单例模式不利于代码调试。在调试过程中，如果单例中的代码没有执行完，也不能模拟生成一个新的对象。
 * 3. 单例模式的功能代码通常写在一个类中，如果功能设计不合理，则很容易违背单一职责原则。
 * <p>
 * 单例模式应用场景
 * 单例模式可以保证在一个JVM中只存在单一实例
 * 1. 频繁创建的对象
 * 2. 要求只能有一个对象时
 * 3. 创建对象耗费资源、时间
 * 4. 频繁创建对象并频繁销毁，如线程、网络连接等
 * 5. 共享对象
 * <p>
 * 单例模式的结构和实现
 * 通常，普通类的构造函数是公有的，外部类可以通过 "new 构造函数()" 来生成多个实例。但是，如果将类的构造函数设为私有的，外部类就无法调用
 * 该构造函数，也就无法生成多个实例。这时该类自身必须定义一个静态私有实例，并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
 */
public class SingletonMode {

    public static void main(String[] args) {
        LazyManSingleton LazyManSingletonInstance = LazyManSingleton.getLazyManSingletonInstance();
    }


}

/**
 * 1. 懒汉式
 * 懒加载；线程不安全
 */
class LazyManSingleton {
    private static LazyManSingleton LazyManSingletonInstance;

    private LazyManSingleton() {
    }

    public static LazyManSingleton getLazyManSingletonInstance() {
        if (LazyManSingletonInstance == null) {
            LazyManSingletonInstance = new LazyManSingleton();
        }
        return LazyManSingletonInstance;
    }

}

/**
 * 2. 懒汉式
 * 懒加载；线程安全，影响效率
 */
class LazyManSyncSingleton {

    // 保证 instance 在所有线程中同步
    private static volatile LazyManSyncSingleton lazyManSyncSingletonInstance;

    //private 避免类在外部被实例化
    private LazyManSyncSingleton() {
    }

    public static synchronized LazyManSyncSingleton getInstance() {
        //getInstance方法前加同步
        if (lazyManSyncSingletonInstance == null) {
            lazyManSyncSingletonInstance = new LazyManSyncSingleton();
        }
        return lazyManSyncSingletonInstance;
    }

}

/**
 * 饿汉式
 * 不是懒加载；线程安全
 */
class HungryManSingleton {
    private static HungryManSingleton hungryManSingleton = new HungryManSingleton();

    private HungryManSingleton() {
    }

    public static HungryManSingleton getInstance() {
        return hungryManSingleton;
    }
}

/**
 * 双重检查锁(double-checked locking)
 * 双锁机制，安全且在多线程情况下能保持较高性能
 */
class DCLSingleton {

    /**
     * 此处对 volatile 关键字进行说明
     * dclSingleton = new DCLSingleton();
     * 通过 javap -c可以看到创建一个对象实例，可以分为三步
     * 1. 分配对象内存
     * 2. 调用构造器方法，执行初始化
     * 3. 将对象引用赋值给变量
     *
     * 虚拟机运行时，指令可能发生重排序。以上2、3可能发生重排序，但是不会重排1的顺序。1这个指令都需要先执行，因为2、3指令需要依托1指令执行结果
     *
     * ————————————————————————————————————————————————————
     *           线程1           线程2
     *  t1      分配内存
     *  t2      变量赋值
     *  t3                      判断对象是否为null
     *  t4                      由于对象不为null，访问该对象
     *  t5      初始化对象
     *  ————————————————————————————————————————————————————
     * 上面错误双重检查锁定的示例代码中，如果线程 1 获取到锁进入创建对象实例，这个时候发生了指令重排序。
     * 当线程1 执行到 t3 时刻，线程 2 刚好进入，由于此时对象已经不为 Null，所以线程 2 可以自由访问该对象。然后该对象还未初始化，所以线程 2 访问时将会发生异常。
     *
     */
    private volatile static DCLSingleton dclSingleton;

    private DCLSingleton() {
    }

    public static DCLSingleton getInstance() {
        if (dclSingleton == null) {
            synchronized (DCLSingleton.class) {
                if (dclSingleton == null) {
                    dclSingleton = new DCLSingleton();
                }
            }
        }
        return dclSingleton;
    }
}



